/* interface/hiled/hiled.c
 *
 * Copyright (c) 2006 Hisilicon Co., Ltd.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 * History:
 *      16-12-2006 Start of Hi3560 CPU support
 */


#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/sched.h>
//#include <asm/hardware.h>
#include <linux/interrupt.h>

#include <linux/poll.h>
#include <linux/miscdevice.h>
#include <linux/errno.h>
#include <linux/fcntl.h>

#include <linux/init.h>
#include <linux/delay.h>
#include <linux/proc_fs.h>
#include <linux/workqueue.h>

#include <asm/uaccess.h>
#include <asm/system.h>
#include <asm/io.h>
#include <linux/version.h>
#include <linux/spinlock.h>
#include <linux/timer.h>

#include "hi_led.h"

#define HI_MUXCTL_BASE  (IO_ADDRESS(0x200F0000))
#define HI_MUXCTL_REG50 (0x0C8) //GPIO9_0 
#define HI_MUXCTL_REG51 (0x0CC) //GPIO9_1 
#define HI_MUXCTL_REG52 (0x0D0) //GPIO9_2 
#define HI_MUXCTL_REG53 (0x0D4) //GPIO9_3 
#define HI_MUXCTL_REG54 (0x0D8) //GPIO9_4 
#define HI_MUXCTL_REG55 (0x0DC) //GPIO9_5 



#define HI_GPIO9_BASE (IO_ADDRESS(0x201D0000))
#define GPIO_DAT(x) (HI_GPIO9_BASE + (1 << (2 + (x))))
#define GPIO_DIR    (0x400)
#define GPIO_IS     (0x404)
#define GPIO_IBE    (0x408)
#define GPIO_IEV    (0x40c)
#define GPIO_IE     (0x410)
#define GPIO_RIS    (0x414)
#define GPIO_MIS    (0x418)
#define GPIO_IC     (0x41c)


/* define macro */
#define WRITE_REG(Addr, Value) ((*(volatile unsigned int *)(Addr)) = (Value))
#define READ_REG(Addr)         (*(volatile unsigned int *)(Addr))

/* debug */
static int g_dbg_flag = 0;
#define HILED_PFX "hiled: "
#define hiled_dbg(params...) \
    do{ \
        if(g_dbg_flag) \
        { \
            printk(KERN_DEBUG HILED_PFX params); \
        } \
    }while(0);


static DEFINE_SPINLOCK(hiled_ioc_lock);

typedef struct LED_TIMER
{
    struct timer_list timer;
    void (*func)(unsigned long);
}LED_TIMER_STRU;
static LED_TIMER_STRU ledtimer;
static LED_CFG_STRU ledcfg[ST_LED_NUM];

static void hiled_config( void )
{
    int i = 0;
    int iev,dirv;
    
    /* TODO: ASIC config pin share */
    WRITE_REG(HI_MUXCTL_BASE+HI_MUXCTL_REG50, 1); //set as gpio
    WRITE_REG(HI_MUXCTL_BASE+HI_MUXCTL_REG51, 1); //set as gpio
    WRITE_REG(HI_MUXCTL_BASE+HI_MUXCTL_REG52, 1); //set as gpio
    WRITE_REG(HI_MUXCTL_BASE+HI_MUXCTL_REG53, 1); //set as gpio
    WRITE_REG(HI_MUXCTL_BASE+HI_MUXCTL_REG54, 1); //set as gpio
    WRITE_REG(HI_MUXCTL_BASE+HI_MUXCTL_REG55, 1); //set as gpio
    
    iev = READ_REG(HI_GPIO9_BASE + GPIO_IE);
    WRITE_REG(HI_GPIO9_BASE + GPIO_IE, iev&0xC0);   //disable interrupt
        
    dirv = READ_REG(HI_GPIO9_BASE + GPIO_DIR);
    WRITE_REG(HI_GPIO9_BASE + GPIO_DIR,dirv|0x3F);  //set gpio9_0 - 9_5 as output
    
    //set all led off
    for ( i = 0 ; i < ST_LED_NUM; i++ )     
    {
         WRITE_REG(GPIO_DAT(i), 0);
    }
    
}

static void  ledtimerfunc(unsigned long arg)
{
    int i; 
    static unsigned int cnt = 0;
    
    //spin_lock(&hiled_ioc_lock);
    
    for ( i = 0 ; i < ST_LED_NUM; i++ )     
    {
        if ( ledcfg[i].freq != 0 )
        {
            if ( ledcfg[i].ison )
            {
                if ( 0 == (cnt++ % (ledcfg[i].freq)) )
                {
                    if ( READ_REG(GPIO_DAT(i)) > 0 )
                    {
                        WRITE_REG(GPIO_DAT(i), 0 );
                    }
                    else
                    {
                        WRITE_REG(GPIO_DAT(i), 1 << i );
                    }
                }
            }
            else
            {
                WRITE_REG(GPIO_DAT(i), 0 << i );
            }
        }
        else
        {
            if ( ledcfg[i].ison )
            {
                WRITE_REG(GPIO_DAT(i), 1 << i );
            }
            else
            {
                WRITE_REG(GPIO_DAT(i), 0 << i );
            }
        }
    }
    //spin_unlock(&hiled_ioc_lock);
    
    ledtimer.timer.expires = jiffies + (HZ >> 2);   // 4 Hz
    ledtimer.timer.data = arg;
    ledtimer.timer.function = ledtimerfunc;
    add_timer(&(ledtimer.timer));
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,36)
static int hiled_ioctl( struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg )
#else
static long hiled_ioctl( struct file *filp, unsigned int cmd, unsigned long arg )
#endif
{
    int i;
    int dirv;
    int tmpdir = 0;
    

    spin_lock(&hiled_ioc_lock);
    
    switch ( cmd )
    {
        case HI_LED_SET_CFG:
            for ( i = 0 ; i < ST_LED_NUM; i++ )
            {
                ledcfg[i].dir = GET_ST_LED_DIR(arg,i);
                tmpdir |= ledcfg[i].dir << i;
                ledcfg[i].ison = GET_ST_LED_ISON(arg,i);
                ledcfg[i].freq = GET_ST_LED_FREQ(arg,i);
            }

            dirv = READ_REG(HI_GPIO9_BASE + GPIO_DIR);
            WRITE_REG(HI_GPIO9_BASE + GPIO_DIR,dirv|tmpdir);
            break;

        default:
            break;
    }
    spin_unlock(&hiled_ioc_lock);
    
    return 0;
}

static ssize_t hiled_read( struct file *filp, char __user *buf, size_t count, loff_t *f_pos )
{
    int i = 0;
    ssize_t n;
    unsigned int c = 0;

    if ( buf == NULL )
    {
        return -1;
    }
    
    for ( i = 0 ; i < ST_LED_NUM ; i++ )
    {
        c = GEN_ST_LED_CFG(c,ledcfg[i].dir,ledcfg[i].ison,ledcfg[i].freq,i);
    }

    n = copy_to_user(buf, &c, sizeof(c));
    
    return n;
}

#if 0
static unsigned int hiled_select( struct file *filp, struct poll_table_struct *wait )
{
    hiled_dbg( "Enter hiir_select.\n" );
    if ( ( hiir_dev.head ) != ( hiir_dev.tail ) )
    {
        return 1;
    }
    poll_wait( filp, &( hiir_dev.irkey_wait ), wait );
    return 0;
}
#endif

static atomic_t hiled_s_available = ATOMIC_INIT( 1 );

static int hiled_open( struct inode *inode, struct file *filp )
{
    if ( ! atomic_dec_and_test( &hiled_s_available ) )
    {
        atomic_inc( &hiled_s_available );
        printk( KERN_DEBUG HILED_PFX "Error: device already open.\n" );
        return -EBUSY;
    }

    spin_lock_init(&hiled_ioc_lock);
    
    memset(ledcfg, 0x0, sizeof(ledcfg));
    init_timer( &(ledtimer.timer) );
    ledtimer.timer.expires = jiffies + (HZ >> 2);
    ledtimer.timer.data = 0;
    ledtimer.timer.function = ledtimerfunc;
    add_timer(&(ledtimer.timer));
    
    return 0;
}

static int hiled_release( struct inode *inode, struct file *filp )
{
    printk("enter release\n");
    del_timer(&(ledtimer.timer));
    ledtimer.func = NULL;
    hiled_config();
    atomic_inc( &hiled_s_available );
    
    return 0;
}

static int hiled_write( struct file *filp, const char __user *buf, size_t count, loff_t *f_pos )
{
    return 0;
}

static struct file_operations hiled_fops =
{
owner   :
    THIS_MODULE,
open    :
    hiled_open,
unlocked_ioctl   :
    hiled_ioctl,
poll    :
    NULL,//hiled_select,
read    :
    hiled_read,
write   :
    hiled_write,
release :
    hiled_release,
};

static struct miscdevice hiled_miscdev =
{
    .minor = MISC_DYNAMIC_MINOR,
    .name  = HILED_DEVICE_NAME,
    .fops  = &hiled_fops,
};

static int __init hiled_init( void )
{
    int res = 0;

    printk( KERN_INFO OSDRV_MODULE_VERSION_STRING "\n" );


    res = misc_register( &hiled_miscdev );
    if ( 0 != res )
    {
        printk( KERN_DEBUG HILED_PFX "Error: can't register\n" );
        return -EFAULT;
    }
    printk( HILED_PFX "init ok. ver=%s, %s.\n", __DATE__, __TIME__ );
    hiled_config();
    
    return res;
}

static void __exit hiled_exit( void )
{
    printk("hiled_exit\n");
    misc_deregister( &hiled_miscdev );
}

module_init( hiled_init );
module_exit( hiled_exit );
MODULE_LICENSE( "GPL" );
MODULE_DESCRIPTION( "Hisilicon GPIO Switch Device Driver" );
MODULE_VERSION( "HI_VERSION=" OSDRV_MODULE_VERSION_STRING );

